/*
 * bencode.c
 * Zie bencode.h voor informatie over onderstaande functies.
 *
 */

#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include "bencode.h"
#include "modify_structs.h"

/*
 * _bencode_string is voor het optimaliseren van het uitbreiden van een tekst.
 * strcat loopt namelijk een hele tekst door op zoek naar de null-byte, terwijl
 * de positie daarvan ook gecachet kan worden.
 * Het wordt gebruikt door de functie add_to_string.
 */
typedef struct {
	char *value;
	size_t length;
} _bencode_string;

/*
 * Deze functie voegt een C-string toe aan een _bencode_string.
 * De functie alloceert zelf geheugen waar nodig.
 */
_bencode_string *add_to_string(_bencode_string *str_ptr, const char *text) {
	if (str_ptr->value == NULL) {
		/*
		 * Alloceer het geheugen als de pointer nog op NULL staat.
		 * Reserveer precies genoeg geheugen voor de tekst en de \0 erachter.
		 */
		str_ptr->length = strlen(text);
		str_ptr->value = (char*)malloc(str_ptr->length+1);
		strcpy(str_ptr->value, text);
	}
	else {
		/*
		 * Vergroot het gereserveerde geheugen om de tekst precies te kunnen bevatten.
		 * Afhankelijk van de implementatie van realloc is het de optimale opslagmethode.
		 */
		size_t new_length = str_ptr->length+strlen(text);
		str_ptr->value = (char*)realloc((void*)str_ptr->value, new_length+1);
		/* kopieer de string vanaf de \0 van de oude tekst */
		strcpy(str_ptr->value+str_ptr->length, text);
		str_ptr->length = new_length;
	}
	return str_ptr;
}

/*
 * Deze functie loopt recursief door de structuur van een entry_t heen.
 * str_ptr wordt telkens meegegeven aan de functie
 * tabcount bevat de recursiediepte en wordt gebruikt voor het bepalen van
 * de insprong in dictionaries of lists.
 */
_bencode_string *_to_string(entry_ptr root, int tabcount, _bencode_string *str_ptr) {
	static char int_str[30]; /* alloceer het geheugen voor het opslaan van een integer eenmalig */

	if (root == NULL) {
		add_to_string(str_ptr, "bad pointer");
		return str_ptr;
	}

	/* maak een C-string voor de correcte insprong */
	char *tabs = (char*)malloc(tabcount+2);
	memset((void*)tabs, (int)'\t', tabcount+1);
	tabs[tabcount] = '\0';

	switch (root->type) {
		case TYPE_DICTIONARY:
			add_to_string(str_ptr, "{\n");
			{
				dict_entry_ptr it;
				for (it = root->data.dictionary->first; it != NULL; it = it->next) {
					add_to_string(str_ptr, tabs); /* zorg voor de juiste insprong */
					add_to_string(str_ptr, "\""); /* noteer de naam tussen aanhalingstekens */
					add_to_string(str_ptr, it->name);
					add_to_string(str_ptr, "\": ");
					_to_string(it->value, tabcount+1, str_ptr); /* gevolgd door de waarde als string */

					if (it->next != NULL)
						add_to_string(str_ptr, ",\n");
					else
						add_to_string(str_ptr, "\n");
				}
			}
			add_to_string(str_ptr, tabs);
			add_to_string(str_ptr, "}");
		break;

		case TYPE_LIST:
			add_to_string(str_ptr, "[\n");
			{
				list_entry_ptr it;
				for (it = root->data.list->first; it != NULL; it = it->next) {
					add_to_string(str_ptr, tabs); /* zorg voor de juiste insprong */
					_to_string(it->value, tabcount+1, str_ptr); /* gevolgd door de waarde als string */

					if (it->next != NULL)
						add_to_string(str_ptr, ",\n");
					else
						add_to_string(str_ptr, "\n");
				}
			}
			add_to_string(str_ptr, tabs);
			add_to_string(str_ptr, "]");

		break;

		case TYPE_STRING:
			add_to_string(str_ptr, "\""); /* noteer de tekst tussen aanhalingstekens */
			add_to_string(str_ptr, *(root->data.string)); /* noteer de tekst */
			add_to_string(str_ptr, "\"");
		break;

		case TYPE_INTEGER:
			/*
			 * In-line aangepaste vorm van itoa()
			 * Originele code uit Kernighan en Ritchies "The C Programming Language".
			 */
			{
				int n = *(root->data.integer), i = 0, j = 0, sign;
				char c;

				if ((sign = n) < 0)  /* record sign */
					n = -n;          /* make n positive */
				do {       /* generate digits in reverse order */
					int_str[i++] = n % 10 + '0';   /* get next digit */
				} while ((n /= 10) > 0);     /* delete it */
				if (sign < 0)
					int_str[i++] = '-';
				int_str[i] = '\0';

				for (i = 0, j = strlen(int_str)-1; i<j; i++, j--) {
					c = int_str[i];
					int_str[i] = int_str[j];
					int_str[j] = c;
				}
			}

			add_to_string(str_ptr, int_str); /* noteer het getal */
		break;
	}

	free(tabs); /* niet vergeten het geheugen terug te geven */
	return str_ptr;
}

char *to_string(entry_ptr root) {
	_bencode_string str;
	str.value = NULL;
	_to_string(root, 0, &str);
	return str.value;
}

#ifndef NO_BENCODE_READING /* laad stdio en onderstaande functies alleen in als er gelezen kan worden */
#include <stdio.h>

entry_ptr parse_bencode_file(FILE *file) {
	entry_ptr root = NULL;
	int c;
	char *str = NULL;
	size_t string_length;
	int value;

	/*
	 * Kijk wat de komende structuur is door het volgende karakter te pakken.
	 * Er wordt vanuit gegaan dat het eind van het bestand niet wordt bereikt
	 * voor de functie (en recursies) klaar is met het lezen.
	 */
	switch (c = fgetc(file)) {
	case 'd': /* dictionary */
		root = dictionary_create();

		/* lees elementen tot er een einde gemarkeerd wordt */
		while ((c = fgetc(file)) != 'e') {
			ungetc(c, file);

			fscanf(file, "%i", &string_length); /* lees het aantal karakters van de key */
			fgetc(file); /* lees de dubbele punt */
			str = (char*)realloc((void*)str, string_length+1);
			fread(str, 1, string_length, file); /* lees de tekens van de key */
			str[string_length] = '\0';
			dictionary_add(root, str, parse_bencode_file(file)); /* lees het element */
		}
		free(str);
	break;

	case 'l': /* list */
		root = list_create();

		/* lees elementen tot er een einde gemarkeerd wordt */
		while ((c = fgetc(file)) != 'e') {
			ungetc(c, file);

			list_add(root, parse_bencode_file(file)); /* lees het element */
		}
	break;

	case 'i': /* integer */
		fscanf(file, "%i", &value); /* lees de waarde */

		if ((c = fgetc(file)) == 'e')
			root = integer_create(value);
	break;

	default: /* string */
		ungetc(c, file);

		fscanf(file, "%i", &string_length); /* lees het aantal karakters van de tekst */
		fgetc(file); /* lees de dubbele punt */
		str = (char*)malloc(string_length+1);
		fread(str, 1, string_length, file); /* lees de karakters */
		str[string_length] = '\0';

		root = string_create(str);

		free(str);
	break;
	}

	return root;
}
#endif /* NO_BENCODE_READING */


#ifdef WITH_BENCODE_TEST
/*
 * Compileer dit bestand met de optie TEST_BENCODE om de to_string-functie
 * in actie te zien.
 */
#include <stdio.h>

int main(int argc, char **argv) {
	string_t my_string;
	my_string = "this is a string";
	integer_t my_int;
	my_int = 42;

	entry_t my_child;
	my_child.type = TYPE_STRING;
	my_child.data._bencode_string = &my_string;

	entry_t my_second_child;
	my_second_child.type = TYPE_INTEGER;
	my_second_child.data.integer = &my_int;

	struct dict_entry_type entry2;
	entry2.name = (string_t)"holds_an_integer";
	entry2.value = &my_second_child;
	entry2.next = NULL;

	struct dict_entry_type entry;
	entry.name = (string_t)"holds_a_string";
	entry.value = &my_child;
	entry.next = &entry2;

	entry_t my_root;
	dictionary_t dict;
	dict.first = &entry;
	my_root.type = TYPE_DICTIONARY;
	my_root.data.dictionary = &dict;

	printf("%s", to_string(&my_root));

	return 0;
}
#endif
